//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "aura.h"
#include "win32.h"
#include "private.h"

AURA_API AuraEvent_Create(
        /* [in] */ Aura_Bool bManualReset,
        /* [in] */ Aura_EventState initialState,
        /* [out] */ Aura_Handle  * phEvent)
{
    EventDescr * pEvent = NULL;

    assert(NULL != phEvent);

    pEvent = (EventDescr *)Aura_malloc(sizeof(EventDescr));
    if (NULL == pEvent) {
        return AURA_E_OUT_OF_MEMORY;
    }
    InitializeCriticalSection(&pEvent->m_hLock);
    pEvent->m_waitList.Initialize();
    pEvent->m_eventState = initialState;
    pEvent->m_bManualReset = bManualReset;
    pEvent->m_pOwner = NULL;

    *phEvent = (Aura_Handle)pEvent;

    return AURA_NOERROR;
}

AURA_API AuraEvent_Wait(
        /* [in] */ Aura_Handle hEvent,
        /* [in] */ Aura_Millisecond32 timeOut,
        /* [out] */ Aura_WaitResult * pResult,
        /* [out] */ Aura_EventState * pState)
{
    ThreadDescr * pSelf = GetCurrentThreadDescr();
    EventDescr * pEvent = (EventDescr *)hEvent;
    DWORD dwRes = 0;
    Aura_ECode ec = AURA_NOERROR;

    assert(NULL != pEvent);
    //assert((timeOut >= 0) || (AURA_INFINITE == timeOut));
    assert(NULL != pResult);

    EnterCriticalSection(&pEvent->m_hLock);
    if (AURA_UNSIGNALED != pEvent->m_eventState) { // if "Signaled";
        if (NULL != pState) {
            *pState = pEvent->m_eventState;
        }
        if (!pEvent->m_bManualReset) {
            pEvent->m_eventState = AURA_UNSIGNALED;
        }
        assert(pEvent->m_waitList.IsEmpty());
        LeaveCriticalSection(&pEvent->m_hLock);

        *pResult = Aura_WaitResult_OK;
        return AURA_NOERROR;
    }

    pSelf->m_eventState = AURA_UNSIGNALED;
    pEvent->m_waitList.InsertLast(&pSelf->m_inWaitList);
    LeaveCriticalSection(&pEvent->m_hLock);

    dwRes = WaitForSingleObjectEx(pSelf->m_hThread, timeOut, TRUE);
    switch (dwRes) {
    case WAIT_IO_COMPLETION:
        if (AURA_UNSIGNALED != pSelf->m_eventState) {
            *pResult = Aura_WaitResult_OK;
            if (NULL != pState) *pState = pSelf->m_eventState;
        }
        else {
            EnterCriticalSection(&pEvent->m_hLock);
            pSelf->m_inWaitList.Detach();
            LeaveCriticalSection(&pEvent->m_hLock);
            *pResult = Aura_WaitResult_Interrupted;
        }
        break;
    case WAIT_TIMEOUT:
        EnterCriticalSection(&pEvent->m_hLock);
        if (!pSelf->m_inWaitList.IsEmpty()) {
            pSelf->m_inWaitList.Detach();
        }
        LeaveCriticalSection(&pEvent->m_hLock);
        *pResult = Aura_WaitResult_TimedOut;
        break;
    case WAIT_OBJECT_0:
    case WAIT_ABANDONED:
        // never reached here. fall through.
    default:
        ec = GetLastECodeError();
        break;
    }

    return ec;
}

STATIC VOID CALLBACK SignalApcFunc(ULONG_PTR dwParam)
{
    // empty function.
}

STATIC Aura_ECode WakeupOneWaiter(EventDescr * pEvent, Aura_EventState state)
{
    DLinkNode * pNode = NULL;
    assert(!pEvent->m_bManualReset);

    EnterCriticalSection(&pEvent->m_hLock);
    if (!pEvent->m_waitList.IsEmpty()) {
        pNode = pEvent->m_waitList.Next();
        pNode->Detach();
        pNode->Initialize();
    }
    else {
        pEvent->m_eventState = state;
    }
    LeaveCriticalSection(&pEvent->m_hLock);

    if (NULL != pNode) {
        ThreadDescr * pThread = THIS(pNode, ThreadDescr, m_inWaitList);
        pThread->m_eventState = state;
        QueueUserAPC(SignalApcFunc, pThread->m_hThread, NULL);
    }

    return AURA_NOERROR;
}

STATIC Aura_ECode WakeupAllWaiters(EventDescr * pEvent, Aura_EventState state)
{
    assert(pEvent->m_bManualReset);

    //TODO: should be more efficient
    EnterCriticalSection(&pEvent->m_hLock);
    while (!pEvent->m_waitList.IsEmpty()) {
        ThreadDescr * pThread = THIS(pEvent->m_waitList.Next(), ThreadDescr, m_inWaitList);
        pThread->m_inWaitList.Detach();
        pThread->m_inWaitList.Initialize();
        pThread->m_eventState = state;
        QueueUserAPC(SignalApcFunc, pThread->m_hThread, NULL);
    }
    pEvent->m_waitList.Initialize();
    pEvent->m_eventState = state;
    LeaveCriticalSection(&pEvent->m_hLock);

    return AURA_NOERROR;
}

AURA_API AuraEvent_Signal(
        /* [in] */ Aura_Handle hEvent,
        /* [in] */ Aura_EventState state)
{
    EventDescr * pEvent = (EventDescr *)hEvent;
    Aura_ECode ec = AURA_NOERROR;

    assert(NULL != pEvent);
    assert(AURA_UNSIGNALED != state);

    if (pEvent->m_bManualReset) {
        ec = WakeupAllWaiters(pEvent, state);
    }
    else {
        ec = WakeupOneWaiter(pEvent, state);
    }

    return ec;
}

AURA_API AuraEvent_Unsignal(
        /* [in] */ Aura_Handle hEvent)
{
    EventDescr * pEvent = (EventDescr *)hEvent;
    assert(NULL != pEvent);

    EnterCriticalSection(&pEvent->m_hLock);
    pEvent->m_eventState = AURA_UNSIGNALED;
    LeaveCriticalSection(&pEvent->m_hLock);

    return AURA_NOERROR;
}

AURA_API AuraEvent_Destroy(
        /* [in] */ Aura_Handle hEvent)
{
    EventDescr * pEvent = (EventDescr *)hEvent;
    assert(NULL != pEvent);

    EnterCriticalSection(&pEvent->m_hLock);
    pEvent->m_eventState = AURA_SIGNALED;
    while(!pEvent->m_waitList.IsEmpty()) {
        ThreadDescr * pThread = NULL;
        pThread = THIS(pEvent->m_waitList.Next(), ThreadDescr, m_inWaitList);
        pThread->m_inWaitList.Detach();
        pThread->m_inWaitList.Initialize();
        pThread->m_eventState = AURA_SIGNALED;
        QueueUserAPC(SignalApcFunc, pThread->m_hThread, NULL);
    }
    LeaveCriticalSection(&pEvent->m_hLock);

    DeleteCriticalSection(&pEvent->m_hLock);
    pEvent->m_waitList.Initialize();
    Aura_free((void *)pEvent);

    return AURA_NOERROR;
}

